import {HTTP_INTERCEPTORS, HttpErrorResponse, HttpEvent} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { AuthService } from '../services/auth.service';
import {BehaviorSubject, observable, Observable, throwError} from 'rxjs';
import {catchError, filter, finalize, switchMap, take} from 'rxjs/operators';

@Injectable()
export class AuthInterceptor implements  HttpInterceptor {

  constructor(private authService: AuthService) { }

  private isRefreshing = false;
  private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  private static addToken(request: HttpRequest<any>, token: string): HttpRequest<any> {
    return request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.authService.getToken();
    if (token) {
      request = AuthInterceptor.addToken(request, this.authService.getToken());
    }
    return next.handle(request).pipe(catchError(error => {
      if (error instanceof HttpErrorResponse && error.status === 401) {
        return this.handle401Error(request, next);
      } else {
        return throwError(error);
      }
    }));
  }

  private handle401Error(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    if (!this.isRefreshing && this.authService.checkRefreshToken()) {
      this.isRefreshing = true;

      // Reset here so that the following requests wait until the token comes back from the refreshToken call
      this.refreshTokenSubject.next(null);

      return this.authService.refreshToken().pipe(
        switchMap((newToken: any) => {
        if (newToken) {
          this.authService.saveToken(newToken);
          this.refreshTokenSubject.next(newToken.refresh_token);
          return next.handle(AuthInterceptor.addToken(request, newToken.access_token));
        }

        // If we don't get a new token, then logout
        this.authService.logout();
        return throwError('no new token');
      }),
        catchError(error => {
          this.authService.logout();
          return throwError('something went wrong');
        }),
        finalize(() => {
          this.isRefreshing = false;
        }), );
    } else {
      return this.refreshTokenSubject.pipe(
        filter(token => token != null),
        take(1),
        switchMap(token => {
          return next.handle(AuthInterceptor.addToken(request, token));
        }),
      );
    }
  }
}

export const authInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
];
